package com.googlecode.mvcguice;

import static org.apache.commons.lang.ClassUtils.PACKAGE_SEPARATOR;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;

public class GuiceServletConextListener implements ServletContextListener {
	private static final Logger LOGGER = LoggerFactory.getLogger(GuiceServletConextListener.class);

	public static final String INJECTOR_NAME = Injector.class.getName();

	private static final String PARAMETER_APP_PACKAGE = "app.package";

	private static final String APP_MODULES_PACKAGE = "modules";
	private static final String APP_CONFIG_MODULE = "Config";

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		final ServletContext servletContext = sce.getServletContext();
		final String appPackage = Strings.nullToEmpty(servletContext.getInitParameter(PARAMETER_APP_PACKAGE)).trim();

		LOGGER.debug("Initializing context with Application package '{}'...", appPackage);
		servletContext.setAttribute(INJECTOR_NAME, getInjector(appPackage));
		LOGGER.info("Injector created. Context initialized.");
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		sce.getServletContext().removeAttribute(INJECTOR_NAME);
		LOGGER.debug("Destroyed context");
	}

	private Injector getInjector(String appPackage) {
		final Module configModule = getAppModule(appPackage, APP_CONFIG_MODULE);
		return Guice.createInjector(configModule);
	}

	private static Module getAppModule(String appPackage, String name) {
		final Class<Module> appModuleClass = getAppModuleClass(appPackage, name);
		if (appModuleClass == null) {
			return null;
		}

		try {
			return appModuleClass.newInstance();
		} catch (InstantiationException e) {
			String msg = "Unable to instantiate module: '" + appModuleClass + "'";
			throw new RuntimeException(msg);
		} catch (IllegalAccessException e) {
			String msg = "Unable to instantiate module: '" + appModuleClass + "' using default constructor";
			throw new RuntimeException(msg);
		}
	}

	@SuppressWarnings("unchecked")
	private static Class<Module> getAppModuleClass(String appPackage, String name) {
		final String appModuleFullClassName = getAppModuleFullClassName(appPackage, name);
		try {
			return (Class<Module>) Class.forName(appModuleFullClassName);
		} catch (ClassNotFoundException e) {
			String msg = "Unable to find required application module class: '" + appModuleFullClassName + "'";
			throw new RuntimeException(msg);
		}
	}

	private static String getAppModuleFullClassName(String appPackage, String name) {
		final String subFullName = APP_MODULES_PACKAGE + PACKAGE_SEPARATOR + name;
		if (appPackage.isEmpty()) {
			return subFullName;
		} else {
			return appPackage + PACKAGE_SEPARATOR + subFullName;
		}
	}
}
